package com.abcd.godating.repository.neo4j;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.traversal.Evaluators;
import org.neo4j.graphdb.traversal.TraversalDescription;
import org.neo4j.kernel.Traversal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.conversion.ConvertedResult;
import org.springframework.data.neo4j.conversion.Handler;
import org.springframework.data.neo4j.conversion.QueryResult;
import org.springframework.data.neo4j.template.Neo4jTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.abcd.godating.constant.SnsProvider;
import com.abcd.godating.service.RelationshipService;

@Repository
public class RelationshipRepository implements RelationshipService {

	private static final Logger logger = Logger.getLogger(RelationshipRepository.class);

	private static final String INDEX_ACCOUNT = "indexAccount";
	private static final String USERID = "userId";

	@Autowired
	private Neo4jTemplate neo4jTemplate;

	@Override
	@Transactional
	public void creatAccount(long userId) {
		createUserNode(userId);
		
		addtestNode();

		Set<Long> userIds = new HashSet<Long>();
		userIds.add(100001l);
		userIds.add(100002l);
		userIds.add(100003l);
		updateFriendShips(userId, SnsProvider.KAIXIN001, userIds);
		
		Set<Long> ids = getFriendShips(userId, SnsProvider.KAIXIN001);
		System.out.println(ids);
		
	}

	/**
	 * TODO: delete it
	 */
	private void addtestNode() {
		createUserNode(100001l);
		createUserNode(100002l);
		createUserNode(100003l);
	}
	
	@Override
	public void addFriendShips(long userId, SnsProvider snsProvider,
			Set<Long> userIds) {
		if (CollectionUtils.isEmpty(userIds)) {
			logger.debug("userIds is empty when add the friendship");
			return;
		}

		Node userNode = getNode(userId);
		if (userNode == null) {
			logger.error("no matched user node for user id: " + userId);
			return;
		}

		for (long id : userIds) {
			Node userFriendNode = getNode(id);
			if (userFriendNode == null) {
				logger.error("no matched user node for user id: " + id);
				// No exception throws, it need to continue the next User Node
			} else {
				Map<String, Object> map = new HashMap<String, Object>();
				neo4jTemplate.createRelationship(userNode, userFriendNode,
						snsProvider, map);
			}
		}
	}
	
	@Override
	public Set<Long> getFriendShips(long userId) {
		Set<Long> userFriends = new HashSet<Long>();
		for (SnsProvider sns : SnsProvider.values()) {
			userFriends.addAll(getFriendShips(userId, sns));
		}
		
		return userFriends;
	}
	
	@Override
	public Set<Long> getFriendShips(long userId, SnsProvider snsProvider) {
		Set<Long> userFriends = new HashSet<Long>();
		
		Node node = getNode(userId);
		if (node == null) {
			logger.error("no matched user node for user id: " + userId);
			return userFriends;
		}
		
		Iterable<Relationship> relationships = node.getRelationships(
				Direction.BOTH, snsProvider);
		if (relationships != null) {
			for (Relationship r : relationships) {
				Node n = r.getOtherNode(node);
				userFriends.add((Long)n.getProperty(USERID));
			}
		}
		
		return userFriends;
	}

	@Override
	public void updateFriendShips(long userId, SnsProvider snsProvider,
			Set<Long> userIds) {
		Node node = getNode(userId);
		if (node == null) {
			logger.error("no matched user node for user id: " + userId);
			return;
		}

		Set<Node> target = new HashSet<Node>();
		for (long id : userIds) {
			Node userFriendNode = getNode(id);
			if (userFriendNode != null) {
				target.add(userFriendNode);
			}else {
				logger.error("no matched user node for user id: " + userId);
			}
		}

		Set<Relationship> toDelete = new HashSet<Relationship>();
		Set<Node> exist = new HashSet<Node>();

		// TODO:
		// potential issue: need Direction.BOTH? how to update the relationship from KAIXIN to GODATING  
		Iterable<Relationship> relationships = node.getRelationships(
				Direction.BOTH, snsProvider);
		if (relationships != null) {
			for (Relationship r : relationships) {
				Node n = r.getOtherNode(node);
				if (!target.contains(n)) {
					toDelete.add(r);
				} else {
					exist.add(n);
				}
			}
		}

		target.removeAll(exist);

		for (Relationship r : toDelete) {
			r.delete();
		}

		for (Node n : target) {
			Map<String, Object> map = new HashMap<String, Object>();
			neo4jTemplate.createRelationship(node, n, snsProvider, map);
		}
	}
	

	@Override
	public void deleteFriendShips(long userId) {
		Node node = getNode(userId);
		if (node == null) {
			logger.error("no matched user node for user id: " + userId);
			return;
		}
		for (SnsProvider sns : SnsProvider.values()) {
			Iterable<Relationship> relationships = node.getRelationships(
					Direction.OUTGOING, sns);
			if (relationships != null) {
				for (Relationship r : relationships) {
					r.delete();
				}
			}
		}		
	}
	
	@Override
	public void deleteFriendShips(long userId, Set<Long> userIds) {
		Node node = getNode(userId);
		if (node == null) {
			logger.error("no matched user node for user id: " + userId);
			return;
		}
		Iterable<Relationship> relationships = node.getRelationships(Direction.OUTGOING);
		if (relationships != null) {
			for (Relationship r : relationships) {
				Node n = r.getEndNode();
				String id = (String)n.getProperty(USERID);
				if (userIds.contains(id)) {
					r.delete();
				}
			}
		}
	}

	@Override
	public Set<Long> getFoaf(long userId, SnsProvider snsProvider, int depth) {
		TraversalDescription td = Traversal.description().breadthFirst()
				.relationships(snsProvider).evaluator(Evaluators.fromDepth(1))
				.evaluator(Evaluators.toDepth(depth))
				.evaluator(Evaluators.excludeStartPosition());
		Node node = getNode(userId);
		final Set<Long> ids = new HashSet<Long>();
		neo4jTemplate.traverse(node, td).handle(new Handler<Path>() {
			@Override
			public void handle(Path path) {
				ids.add((Long) path.endNode().getProperty(USERID));
			}
		});
		
		return ids;
	}
	
	/**
	 * create the suer node
	 * 
	 * @param userId
	 * @return
	 */
	private Node createUserNode(long userId) {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put(USERID, userId);
		Node node = neo4jTemplate.createNode(map);
		neo4jTemplate.index(INDEX_ACCOUNT, node, USERID, userId);

		return node;
	}
	
	/**
	 * get the user node
	 * 
	 * @param userId
	 * @return
	 */
	private Node getNode(long userId) {
		QueryResult<PropertyContainer> queryResult = neo4jTemplate.lookup(
				INDEX_ACCOUNT, USERID, userId);
		Node node = null;
		if (queryResult != null) {
			ConvertedResult<Node> nodeResult = queryResult.to(Node.class);
			if (nodeResult != null) {
				node = nodeResult.single();
			}
		}
		
		return node;
	}

}
